/*
 *  linux/fs/inode.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include <string.h>
#include <sys/stat.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <asm/system.h>

struct m_inode inode_table[NR_INODE] = { { 0, }, };

static void read_inode(struct m_inode * inode);
static void write_inode(struct m_inode * inode);

static inline void wait_on_inode(struct m_inode * inode) {
	cli();
	while (inode->i_lock)
		sleep_on(&inode->i_wait);
	sti();
}

static inline void lock_inode(struct m_inode * inode) {
	cli();
	while (inode->i_lock)
		sleep_on(&inode->i_wait);
	inode->i_lock = 1;
	sti();
}

static inline void unlock_inode(struct m_inode * inode) {
	inode->i_lock = 0;
	wake_up(&inode->i_wait);
}

void invalidate_inodes(int dev) {
	int i;
	struct m_inode * inode;

	inode = 0 + inode_table;
	for (i = 0; i < NR_INODE; i++, inode++) {
		wait_on_inode(inode);
		if (inode->i_dev == dev) {
			if (inode->i_count)
				printk("inode in use on removed disk\n\r");
			inode->i_dev = inode->i_dirt = 0;
		}
	}
}

void sync_inodes(void) {
	int i;
	struct m_inode * inode;

	inode = 0 + inode_table;
	for (i = 0; i < NR_INODE; i++, inode++) {
		wait_on_inode(inode);
		if (inode->i_dirt && !inode->i_pipe)
			write_inode(inode);
	}
}

static int _bmap(struct m_inode * inode, int block, int create) {
	struct buffer_head * bh;
	int i;

	if (block < 0)
		panic("_bmap: block<0");
	if (block >= 7 + 512 + 512 * 512)
		panic("_bmap: block>big");
	if (block < 7) {
		if (create && !inode->i_zone[block])
			if ((inode->i_zone[block] = new_block(inode->i_dev))) {
				inode->i_ctime = CURRENT_TIME;
				inode->i_dirt = 1;
			}
		return inode->i_zone[block];
	}
	block -= 7;
	if (block < 512) {
		if (create && !inode->i_zone[7])
			if ((inode->i_zone[7] = new_block(inode->i_dev))) {
				inode->i_dirt = 1;
				inode->i_ctime = CURRENT_TIME;
			}
		if (!inode->i_zone[7])
			return 0;
		if (!(bh = bread(inode->i_dev, inode->i_zone[7])))
			return 0;
		i = ((unsigned short *) (bh->b_data))[block];
		if (create && !i)
			if ((i = new_block(inode->i_dev))) {
				((unsigned short *) (bh->b_data))[block] = i;
				bh->b_dirt = 1;
			}
		brelse(bh);
		return i;
	}
	block -= 512;
	if (create && !inode->i_zone[8])
		if ((inode->i_zone[8] = new_block(inode->i_dev))) {
			inode->i_dirt = 1;
			inode->i_ctime = CURRENT_TIME;
		}
	if (!inode->i_zone[8])
		return 0;
	if (!(bh = bread(inode->i_dev, inode->i_zone[8])))
		return 0;
	i = ((unsigned short *) bh->b_data)[block >> 9];
	if (create && !i)
		if ((i = new_block(inode->i_dev))) {
			((unsigned short *) (bh->b_data))[block >> 9] = i;
			bh->b_dirt = 1;
		}
	brelse(bh);
	if (!i)
		return 0;
	if (!(bh = bread(inode->i_dev, i)))
		return 0;
	i = ((unsigned short *) bh->b_data)[block & 511];
	if (create && !i)
		if ((i = new_block(inode->i_dev))) {
			((unsigned short *) (bh->b_data))[block & 511] = i;
			bh->b_dirt = 1;
		}
	brelse(bh);
	return i;
}

int bmap(struct m_inode * inode, int block) {
	return _bmap(inode, block, 0);
}

int create_block(struct m_inode * inode, int block) {
	return _bmap(inode, block, 1);
}

void iput(struct m_inode * inode) {
	struct task_struct* current = get_current_task();
	if (!inode)
		return;
	wait_on_inode(inode);
	if (!inode->i_count) {
		//printk("inputPid: %d, root: %u\n\r", current->pid, (unsigned long)inode);
		panic("iput: trying to free free inode");
	}
	if (inode->i_pipe) {
		wake_up(&inode->i_wait);
		if (--inode->i_count)
			return;
		if (!free_page(inode->i_size))
					panic("iput: trying to free free page");
		//free_page(inode->i_size);
		inode->i_count = 0;
		inode->i_dirt = 0;
		inode->i_pipe = 0;
		return;
	}
	if (!inode->i_dev) {
		inode->i_count--;
		return;
	}
	if (S_ISBLK(inode->i_mode)) {
		sync_dev(inode->i_zone[0]);
		wait_on_inode(inode);
	}
	repeat: if (inode->i_count > 1) {
		inode->i_count--;
		/*if (inode->i_num == 1 && current->pid == 4) {
		 printk("release root, pid: %d, rootCount: %d\n\r", current->pid,
		 inode->i_count);
		 }*/
		return;
	}
	if (!inode->i_nlinks) {
		truncate(inode);
		free_inode(inode);
		return;
	}
	if (inode->i_dirt) {
		write_inode(inode); /* we can sleep - so do again */
		wait_on_inode(inode);
		//printk("inode is dirt, icount: %d, root: %u\n\r", inode->i_count, inode);
		goto repeat;
	}
	inode->i_count--;
	/*if (inode->i_num == 1 && current->pid == 4) {
	 printk("release root, pid: %d, rootCount: %d\n\r", current->pid,
	 inode->i_count);
	 }*/
	return;
}

struct m_inode * get_empty_inode(void) {
	struct m_inode * inode;
	static struct m_inode * last_inode = inode_table;
	int i;

	do {
		inode = NULL;
		for (i = NR_INODE; i; i--) {
			if (++last_inode >= inode_table + NR_INODE)
				last_inode = inode_table;
			if (!last_inode->i_count) {
				inode = last_inode;
				if (!inode->i_dirt && !inode->i_lock)
					break;
			}
		}
		if (!inode) {
			for (i = 0; i < NR_INODE; i++)
				printk("%04x: %6d\t", inode_table[i].i_dev,
						inode_table[i].i_num);
			panic("No free inodes in mem");
		}
		wait_on_inode(inode);
		while (inode->i_dirt) {
			write_inode(inode);
			wait_on_inode(inode);
		}
	} while (inode->i_count);
	/*if (current->pid == 4)
		printk(
				"memeset before,pid: %d, inum: %d, icount: %d, idev: %d, iatime: %d, ictime: %d, addr: %p\n\r",
				current->pid, inode->i_num, inode->i_count, inode->i_dev,
				inode->i_atime, inode->i_ctime, inode);*/
	memset(inode, 0, sizeof(*inode));
	inode->i_count = 1;
	/*if (current->pid == 4)
		printk(
				"memeset after,pid: %d, inum: %d, icount: %d, idev: %d, iatime: %d, ictime: %d, addr: %p\n\r",
				current->pid, inode->i_num, inode->i_count, inode->i_dev,
				inode->i_atime, inode->i_ctime, inode);*/
	return inode;
}

struct m_inode * get_pipe_inode(void) {
	struct m_inode * inode;

	if (!(inode = get_empty_inode()))
		return NULL;
	if (!(inode->i_size = get_free_page(PAGE_IN_MEM_MAP))) {
		inode->i_count = 0;
		return NULL;
	}
	inode->i_count = 2; /* sum of readers/writers */
	PIPE_HEAD (*inode) = PIPE_TAIL(*inode) = 0;
	inode->i_pipe = 1;
	return inode;
}

struct m_inode * iget(int dev, int nr) {
	struct m_inode * inode, *empty;

	if (!dev)
		panic("iget with dev==0");
	empty = get_empty_inode();

	inode = inode_table;
	while (inode < NR_INODE + inode_table) {
		if (inode->i_dev != dev || inode->i_num != nr) {
			inode++;
			continue;
		}
		wait_on_inode(inode);
		if (inode->i_dev != dev || inode->i_num != nr) {
			inode = inode_table;
			continue;
		}
		inode->i_count++;
		if (inode->i_mount) {
			int i;

			for (i = 0; i < NR_SUPER; i++)
				if (super_block[i].s_imount == inode)
					break;
			if (i >= NR_SUPER) {
				printk("Mounted inode wasn't got sb\n");
				if (empty)
					iput(empty);
				return inode;
			}
			iput(inode);
			dev = super_block[i].s_dev;
			nr = ROOT_INO;
			inode = inode_table;
			continue;
		}
		if (empty)
			iput(empty);
		return inode;
	}

	//panic("Get empty inode2\n");

	/*if (current->pid == 4)
	 printk("igetParams,dev: %d, nr: %d\n\r", dev, nr);*/

	if (!empty)
		return (NULL);
	inode = empty;
	inode->i_dev = dev;
	inode->i_num = nr;
	read_inode(inode);
	return inode;
}

static void read_inode(struct m_inode * inode) {
	struct super_block * sb;
	struct buffer_head * bh;
	int block;

	lock_inode(inode);
	if (!(sb = get_super(inode->i_dev)))
		panic("trying to read inode without dev");
	block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks
			+ (inode->i_num - 1) / INODES_PER_BLOCK;
	if (!(bh = bread(inode->i_dev, block)))
		panic("unable to read i-node block");
	*(struct d_inode *) inode = ((struct d_inode *) bh->b_data)[(inode->i_num
			- 1) % INODES_PER_BLOCK];
	brelse(bh);
	unlock_inode(inode);
}

static void write_inode(struct m_inode * inode) {
	struct super_block * sb;
	struct buffer_head * bh;
	int block;

	lock_inode(inode);
	if (!inode->i_dirt || !inode->i_dev) {
		unlock_inode(inode);
		return;
	}
	if (!(sb = get_super(inode->i_dev)))
		panic("trying to write inode without device");
	block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks
			+ (inode->i_num - 1) / INODES_PER_BLOCK;
	if (!(bh = bread(inode->i_dev, block)))
		panic("unable to read i-node block");
	((struct d_inode *) bh->b_data)[(inode->i_num - 1) % INODES_PER_BLOCK] =
			*(struct d_inode *) inode;
	bh->b_dirt = 1;
	inode->i_dirt = 0;
	brelse(bh);
	unlock_inode(inode);
}
